/*
 *
 *
Copyright (c) 1998-2001 Aristar, Inc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any,
must include the following acknowledgment:

"This product includes software developed by Aristar, Inc. (http://www.aristar.com/)."

Alternately, this acknowledgment may appear in the software itself, if and
wherever such third-party acknowledgments normally appear.

4. The names "Dialect" and "Aristar, Inc." must not be used to endorse or
promote products derived from this software without prior written permission.
For written permission, please contact info@aristar.com.

5. Products derived from this software may not be called "Dialect", nor may
"Dialect" appear in their name, without prior written permission of Aristar, Inc.

THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
ARISTAR, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software consists of voluntary contributions made by many individuals
on behalf of Aristar, Inc.  For more information on Aristar, Inc.,
please see <http://www.aristar.com/>.

Portions of this software are based upon software freely available and
written by individuals not associated with Aristar, Inc.  Copyrights are
maintained in the relevant source code files.
 *
 *
 */

#include "stdafx.h"
#include "TTable.h"

///////////////////////////////////
#if defined(USING_MFC) && defined(_DEBUG)
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
///////////////////////////////////

TTable::TTable(CArrayImpl* mainArray, CArrayImpl* rowHeaders, CArrayImpl* colHeaders, int pad)
{
	m_Pad = pad;
	ASSERT(mainArray != NULL);
	m_Rows = mainArray->m_Length;

	// We currently assume that all rows in the main array have the
	// same length, so checking the length of the first row should give
	// us the number of columns to print
	VALUE firstRow = mainArray->__get__(1);		// Remember, one based, not zero
	if (firstRow.Type() != stArray) {
		RunTimeError(_T("Expecting two dimensional array in table statement."));
	}
	m_Cols = firstRow.Array()->m_Length;

	if (rowHeaders != NULL) {
		if (rowHeaders->m_Length > 0) {
			m_bRowHeaders = true;
			++m_Cols;	// Need an extra column for the row headers
		}
		else m_bRowHeaders = false;
	}
	if (colHeaders != NULL) {
		if (colHeaders->m_Length > 0) {
			m_bColHeaders = true;
			++m_Rows;	// Need an extra row for the column headers
		}
		else m_bColHeaders = false;
	}

	// RemoveUnsusedRowCol() will remove empty rows and columns
	// For each that it removes, it'll set the corresponding
	// element to true in RowRemoved and ColRemoved
	m_bEmptyRow = new bool[m_Rows];
	m_bEmptyCol = new bool[m_Cols];
	MarkUnsusedRowCol(mainArray);

	m_Cells = new TTableCell[m_Rows * m_Cols];
	FillMainArray(mainArray);
	if (m_bRowHeaders) FillRowHeaderCells(rowHeaders);
	if (m_bColHeaders) FillColHeaderCells(colHeaders);
}

TTable::~TTable()
{
	delete[] m_Cells;
	delete[] m_bEmptyRow;
	delete[] m_bEmptyCol;
}

void TTable::MarkUnsusedRowCol(CArrayImpl* mainArray)
{
	int rowOff = m_bColHeaders ? 1 : 0;
	int colOff = m_bRowHeaders ? 1 : 0;

	// Assume all are not to be printed, then if we
	// see anything at all then we know otherwise by
	// setting these to false
	int idx;
	for (idx = 0; idx < m_Cols; ++idx) m_bEmptyCol[idx] = true;
	for (idx = 0; idx < m_Rows; ++idx) m_bEmptyRow[idx] = true;
	if (m_bRowHeaders) m_bEmptyCol[0] = false;	// Always there is exist
	if (m_bColHeaders) m_bEmptyRow[0] = false;	// Always there is exist

	VALUE rowArray;
	TString cellVal;
	for (int row = 0; row < mainArray->m_Length; ++row) {
		rowArray = mainArray->__get__(row + 1);	// one based
		ASSERT(rowArray.Type() == stArray);
		for (int col = 0; col < rowArray.Array()->m_Length; ++col) {
			cellVal = rowArray.Array()->__get__(col + 1).String();
			if (!cellVal.IsEmpty()) {
				m_bEmptyRow[row + rowOff] = false;
				m_bEmptyCol[col + colOff] = false;
			}
		}
	}
}

void TTable::FillMainArray(CArrayImpl* mainArray)
{
	// When indexing into m_Cells...
	// If we have column headers then row cells start at one here, else zero
	// ditto for row headers and column cells
	int rowOff = m_bColHeaders ? 1 : 0;
	int colOff = m_bRowHeaders ? 1 : 0;

	for (int row = 0; row < mainArray->m_Length; ++row) {
		if (m_bEmptyRow[row + rowOff] == false) {
			VALUE rowArray = mainArray->__get__(row + 1);	// one based
			ASSERT(rowArray.Type() == stArray);
			for (int col = 0; col < rowArray.Array()->m_Length; ++col) {
				if (m_bEmptyCol[col + colOff] == false) {
					SetCell(row + rowOff, col + colOff, rowArray.Array()->__get__(col + 1));
				}
			}
		}
	}
}

void TTable::FillColHeaderCells(CArrayImpl* colHeaders)
{
	if (colHeaders != NULL) {
		if (colHeaders->m_Length != m_Cols) {
			RunTimeError(_T("Number of column headers doesn't match number of columns in table."));
		}
		else {
			// The first column header sits over the row headers, if there are row headers
			// so column indexing starts at zero
			for (int col = 0; col < m_Cols; ++col) {
				// int idx = 0 * m_Cols + col;
				if (m_bEmptyCol[col] == false) {
					m_Cells[col].contents = colHeaders->__get__(col + 1).String();
				}
			}
		}
	}
}

void TTable::FillRowHeaderCells(CArrayImpl* rowHeaders)
{
	if (rowHeaders != NULL) {
		if (((rowHeaders->m_Length != m_Rows) && !m_bColHeaders) ||
			((rowHeaders->m_Length != m_Rows - 1) && m_bColHeaders))
		{
			RunTimeError(_T("Number of row headers doesn't match number of rows in table."));
		}
		else {
			// The first row header sits under the column header row, if there are col headers
			// so column indexing starts at one if column heades, else zero
			int row = m_bColHeaders ? 1 : 0;
			int headerIdx = 1;

			while (row < m_Rows) {
				int idx = row * m_Cols;	// The first column in each row
				m_Cells[idx].contents = rowHeaders->__get__(headerIdx++).String();
				++row;
			}
		}
	}
}

void TTable::SetCell(int row, int col, const VALUE& val, TTableCell::AlignTypes align, TCHAR fill)
{
	if ((row >= m_Rows) || (col >= m_Cols)) {
		InternalError(_T("Illegal attempt to set row or column in table"));
	}
	else {
		int idx = row * m_Cols + col;
		m_Cells[idx].contents = val.String();
		m_Cells[idx].alignment = align;
		m_Cells[idx].fill = fill;
	}
}

const TTableCell& TTable::GetCell(int row, int col) const
{
	if ((row >= m_Rows) || (col >= m_Cols)) {
		InternalError(_T("Illegal attempt to set row or column in table"));
	}
	return m_Cells[row * m_Cols + col];
}
